﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace BeholdTV
{
    public class RemoteControl : IDisposable
    {
        private const int DefaultPollingTimeout = 100;
        private const int DefaultPauseTimeout = 500;
        private const uint NoButtonPressed = 0xFF;

        private readonly Thread pollingThread;
        private readonly ManualResetEvent pollingEvent;
		private BeholdTVApiWrapper api;
		private int pollingTimeout;
        private int pauseTimeout;
        private bool initialized;
        private RemoteControlType remoteControlType = RemoteControlType.Unknown;

        public RemoteControl()
        {
            api = new BeholdTVApiWrapper();

            Cards = GetCards();

            pollingEvent = new ManualResetEvent(false);

            pollingThread = new Thread(DoPolling);
            pollingThread.IsBackground = true;
            pollingThread.Start();
        }

        public List<string> Cards
        {
            get;
            private set;
        }

        public RemoteControlType RemoteControlType
        {
            get
            {
                if (!initialized)
                    throw new BeholdTVCardIsNotInitializedException();

                return remoteControlType;
            }
        }

        /// <summary>
        /// WARNING: raised in another thread!
        /// </summary>
        public event EventHandler<RemoteControlButtonEventArgs> ButtonPressed;

        /// <summary>
        /// WARNING: raised in another thread!
        /// </summary>
        public event EventHandler<RemoteControlButtonEventArgs> ButtonReleased;


        public void InitializeCard(int cardIndex)
        {
            StopPolling();
            
            initialized = api.OpenCard(cardIndex);
            remoteControlType = initialized ? api.GetRemoteType() : RemoteControlType.Unknown;
        }

        public void StartPolling()
        {
            StartPolling(DefaultPollingTimeout, DefaultPauseTimeout);
        }

        public void StartPolling(int pollingTimeout)
        {
            StartPolling(pollingTimeout, DefaultPauseTimeout);
        }

        public void StartPolling(int pollingTimeout, int pauseTimeout)
        {
            StopPolling();

            if (!initialized)
                throw new Exception("Card is not initialized! Call InitializeCard method first.");

            this.pollingTimeout = pollingTimeout;
            this.pauseTimeout = pauseTimeout;

            pollingEvent.Set();
        }

        public void StopPolling()
        {
            pollingEvent.Reset();
        }

        public void Dispose()
        {
            if (api == null)
                return;

            StopPolling();

            api.Dispose();
            api = null;
        }

        protected virtual void OnButtonPressed(RemoteControlButtonEventArgs e)
        {
            if (ButtonPressed != null)
                ButtonPressed(this, e);
        }

        protected virtual void OnButtonReleased(RemoteControlButtonEventArgs e)
        {
            if (ButtonReleased != null)
                ButtonReleased(this, e);
        }

        private void DoPolling()
        {
            var prevCode = NoButtonPressed;
            var prevCodeEx = (uint)0;
            while (true)
            {
                try
                {
                    pollingEvent.WaitOne();

                    var code = api.GetRemoteCode();
                    var codeEx = api.GetRemoteCodeEx();

                    if (prevCode != NoButtonPressed)
                        OnButtonReleased(new RemoteControlButtonEventArgs(prevCode, prevCodeEx));

                    if (code != NoButtonPressed)
                        OnButtonPressed(new RemoteControlButtonEventArgs(code, codeEx));

                    prevCode = code;
                    prevCodeEx = codeEx;

                    if (code == prevCode)
                        Thread.Sleep(pollingTimeout);
                    else
                        Thread.Sleep(pauseTimeout);
                }
                catch (ThreadInterruptedException)
                {
                    return;
                }
                catch (ThreadAbortException)
                {
                    return;
                }
            }
        }

        private List<string> GetCards()
        {
            var n = api.GetCardCount();

            var result = new List<string>(n);
            for (var i = 0; i < n; i++)
                result.Add(api.GetCardName(i));

            return result;
        }
    }
}
